import datetime
import pandas as pd
import backtrader as bt
import backtrader.indicators as btind
from datetime import datetime
import akshare as ak
 
class SmaCross(bt.Strategy):
 
    params = (
        ('period_sma10', 5),
        ('period_sma30', 10)
    )
	
	# 打印日志
    def log(self, txt, dt=None):

        dt = dt or self.data.datetime.date(0)
        print('%s, %s' % (dt, txt))

    def __init__(self):
		
		# 用于保存订单
        self.order = None
        # 订单价格
        self.buyprice = None
        # 订单佣金
        self.buycomm = None
		#买卖数量
        self.ordersize = 100
		# 定义变量保存所有收盘价
        self.dataclose = self.data.close
		
		#计算10日均线
        self.sma10 = btind.MovingAverageSimple(self.dataclose, period=self.params.period_sma10)
        # 计算30日均线
        self.sma30 = btind.MovingAverageSimple(self.dataclose, period=self.params.period_sma30)

    def notify_order(self, order):
		
		# 等待订单提交、订单被cerebro接受
        if order.status in [order.Submitted, order.Accepted]:
            return
		
		# 等待订单完成
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))

            else:
                self.log(
                    'SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))
		
		# 如果订单保证金不足，将不会完成，而是执行以下拒绝程序
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')

        self.order = None

    def notify_trade(self, trade):

        if not trade.isclosed:
            return

        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                 (trade.pnl, trade.pnlcomm))  # pnl：盈利  pnlcomm：手续费
	
	# 策略逻辑实现
    def next(self):
		# 当今天的10日均线大于30日均线并且昨天的10日均线小于30日均线，则进入市场（买）
        if self.sma10[0] > self.sma30[0] and self.sma10[-1] < self.sma30[-1]:
        	# 判断订单是否完成，完成则为None，否则为订单信息
            if self.order:
                return
			
			#若上一个订单处理完成，可继续执行买入操作
            self.order = self.buy(size = self.ordersize)
            
		# 当今天的10日均线小于30日均线并且昨天的10日均线大于30日均线，则退出市场（卖）
        elif self.sma10[0] < self.sma30[0] and self.sma10[-1] > self.sma30[-1]:
        	# 卖出
            self.order = self.sell(size = self.ordersize)
        self.printData()

    def printData(self):
        for i, d in enumerate(self.datas):
            pos = self.getposition(d)
            if len(pos):
                print('日期：{} 持仓:{}, 成本价:{}, 当前价:{}, 盈亏:{:.2f}'.format(
                    self.data.datetime.date(), pos.size, pos.price, pos.adjbase, pos.size * (pos.adjbase - pos.price)))
        print( '---------------------------------------')


def bt1():
 
    # 利用 AKShare 获取股票的后复权数据，这里只获取前 6 列
    stock_hfq_df = ak.stock_zh_a_hist(symbol="600592", adjust="qfq", start_date="20200101", end_date="20230825").iloc[:, :6]
    # 处理字段命名，以符合 Backtrader 的要求
    stock_hfq_df.columns = [
    'date',
    'open',
    'close',
    'high',
    'low',
    'volume',
    ]
    # 把 date 作为日期索引，以符合 Backtrader 的要求
    stock_hfq_df.index = pd.to_datetime(stock_hfq_df['date'])
    print(stock_hfq_df)
    start_date = datetime(2023, 1, 1)  # 回测开始时间
    end_date = datetime(2023, 8, 25)  # 回测结束时间
    data = bt.feeds.PandasData(dataname=stock_hfq_df, 
                           fromdate=start_date, 
                           todate=end_date)  # 加载数据
 
    # 初始化cerebro回测系统设置
    cerebro = bt.Cerebro()
    # 将数据传入回测系统
    cerebro.adddata(data)
    # 将交易策略加载到回测系统中
    cerebro.addstrategy(SmaCross)
    # 设置初始资本为10,000
    startcash = 100000
    cerebro.broker.setcash(startcash)
    # 设置交易手续费为 0.1%
    cerebro.broker.setcommission(commission=0.001)
 
    # 运行回测系统
    result = cerebro.run()

    # 获取回测结束后的总资金
    portvalue = cerebro.broker.getvalue()
    pnl = portvalue - startcash
    # 打印结果
    print(f'净收益: {round(pnl,2)}')
    print(f'总资金: {round(portvalue,2)}')

    cerebro.addobserver(bt.observers.Broker)
    cerebro.addobserver(bt.observers.Trades)
    cerebro.addobserver(bt.observers.BuySell)
    cerebro.addobserver(bt.observers.DrawDown)
    cerebro.addobserver(bt.observers.TimeReturn)
    cerebro.plot(style='candlestick')
 
 
if __name__ == '__main__':
    bt1()